<template>
  <div class="clip-upload">
    <!-- 上传区域 -->
    <div
      @paste="handlePaste"
      @dragover.prevent="handleDragOver"
      @dragleave.prevent="handleDragLeave"
      @drop.prevent="handleDrop"
      :class="['clip-area', { 'disabled-upload': disabled }]"
      :style="{
        borderColor: styleConfig.borderColor,
        backgroundColor: styleConfig.bgColor,
      }"
    >
      <div class="flex flex-col items-center justify-center space-y-3">
        <p>
          <img
            v-if="styleConfig.uploadImg"
            :src="styleConfig.uploadImg"
            alt="上传图标"
          />
          <slot name="uploadIcon"></slot>
        </p>
        <div class="text-center">
          <p :style="{ color: styleConfig.detailColor }">
            粘贴、拖拽至此处，或
            <span
              :class="['upload-click', { 'disabled-upload': disabled }]"
              @click.stop="handleClickUpload"
              >点击上传</span
            >
          </p>
        </div>
      </div>
    </div>

    <!-- 文件选择 -->
    <input
      ref="fileInput"
      type="file"
      :accept="acceptedFileTypes"
      :multiple="limits > 1"
      @change="handleFileSelect"
      class="hidden"
    />
  </div>
</template>

<script setup>
import { ref, shallowRef, computed, toRefs } from 'vue';
import { ElMessage } from 'element-plus';

const props = defineProps({
  /**
   * 上传区域样式配置
   */
  styleConfig: {
    type: Object,
    default: () => ({
      borderColor: '#ccc',
      bgColor: '#fff',
      uploadImg: '',
      detailColor: '#e9451a',
    }),
  },

  /**
   * 最大文件数量
   */
  limits: {
    type: Number,
    default: 10,
  },

  /**
   * 可上传的文件类型
   */
  fileType: {
    type: Array,
    default: () => [],
  },

  /**
   * 是否禁用上传
   */
  disabled: {
    type: Boolean,
    default: false,
  },

  maxSize: {
    type: Number,
    default: 100,
  },
});

const { styleConfig, limits, disabled, maxSize } = toRefs(props);

const emits = defineEmits(['on-upload']);

const fileInput = shallowRef(null);

/**
 * 拖拽状态
 */
const isDragging = ref(false);

/**
 * 文件扩展名到MIME类型的映射表
 */
const typeToMime = {
  // 图片类型
  jpg: 'image/jpeg',
  jpeg: 'image/jpeg',
  png: 'image/png',
  bmp: 'image/bmp',
  gif: 'image/gif',

  // 文档类型
  pdf: 'application/pdf',
  doc: 'application/msword',
  docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  xls: 'application/vnd.ms-excel',
  xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  txt: 'text/plain',

  // 压缩文件
  rar: 'application/x-rar-compressed',
  zip: 'application/zip',

  // 音视频
  mp4: 'video/mp4',
  mp3: 'audio/mpeg',
  mov: 'video/quicktime',
};

/**
 * 计算接受的文件类型
 */
const acceptedFileTypes = computed(() => {
  if (props.fileType?.length) {
    return props.fileType.map((type) => typeToMime[type] || '').join(',');
  }
  // 基于typeToMime的所有MIME类型
  return Object.values(typeToMime).join(',');
});

/**
 * 触发文件选择
 */
const handleClickUpload = () => {
  if (disabled.value) return;
  fileInput.value?.click();
};

/**
 * 处理文件选择
 * @param event
 */
const handleFileSelect = (event) => {
  const files = Array.from(event.target.files || []);
  processFiles(files);
  // 清空input，允许重复选择同一文件
  event.target.value = '';
};

/**
 * 处理粘贴事件
 * @param event
 */
const handlePaste = (event) => {
  if (disabled.value) return;

  const items = event.clipboardData?.items;
  if (!items) return;

  const files = [];
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    if (item.kind === 'file') {
      const file = item.getAsFile();
      file && files.push(file);
    }
  }

  if (files.length) {
    event.preventDefault();
    processFiles(files);
  }
};

/**
 * 处理拖拽进入
 */
const handleDragOver = () => {
  isDragging.value = true;
};

/**
 * 处理拖拽离开
 */
const handleDragLeave = () => {
  isDragging.value = false;
};

/**
 * 处理文件拖放
 * @param event
 */
const handleDrop = (event) => {
  if (disabled.value) return;
  isDragging.value = false;
  const files = Array.from(event.dataTransfer?.files || []);
  processFiles(files);
};

/**
 * 处理文件，并对外抛出有效文件
 * @param files
 */
const processFiles = (files) => {
  let tempFiles = [];
  // 检查文件数量
  if (files.length > limits.value) {
    ElMessage.error(`注意：一次性最多只能上传 ${limits.value} 个文件`);
    tempFiles = files.slice(0, limits.value);
  } else {
    tempFiles = files;
  }

  // 验证文件类型
  const validFiles = [];
  // 获取可接收文件类型
  const validTypes = props.fileType?.length
    ? props.fileType
    : Object.keys(typeToMime);
  for (const file of tempFiles) {
    if (isValidFileType(file, validTypes)) {
      // 校验文件大小
      const fileSizeInMB = file.size / (1024 * 1024);
      if (fileSizeInMB > maxSize.value) {
        ElMessage.error(
          `文件 "${file.name}" 大小超过限制，最大允许 ${maxSize.value}MB`
        );
        continue;
      }
      validFiles.push(file);
    } else {
      const validTypesStr = validTypes.join(', ').toLowerCase();
      ElMessage.error(
        `文件 "${file.name}" 类型不支持。仅支持: ${validTypesStr}`
      );
    }
  }

  // 添加有效文件
  if (validFiles.length) {
    console.log([...validFiles]);

    emits('on-upload', [...validFiles]);
  }
};

/**
 * 验证文件类型
 * @param file
 */
const isValidFileType = (file, validTypes) => {
  const fileExtension = file.name.split('.').pop()?.toLowerCase();
  return validTypes.includes(fileExtension);
};
</script>
<style scoped lang="less">
.clip-upload {
  font-size: 14px;

  .clip-area {
    position: relative;
    border: 2px dashed #ccc;
    border-radius: 8px;
    transition: all 0.3s ease-in-out;
    padding: 40px 20px;
    &:hover {
      opacity: 0.7;
    }
  }

  .upload-click {
    color: #567ff2;
    cursor: pointer;
  }

  .disabled-upload {
    cursor: not-allowed;
    opacity: 0.5;

    &:hover {
      opacity: 0.5;
    }
  }
}
</style>
